﻿using PMS.Web.Framework.Models;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace PMS.Web.Framework.ModelUtils
{
    public static class ReflectionExtensions
    {
        public static T GetAttribute<T>(this MemberInfo member, bool isRequired)
                where T : Attribute
        {
            var attribute = member.GetCustomAttributes(typeof(T), false).SingleOrDefault();

            if (attribute == null && isRequired)
            {
                throw new ArgumentException(
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "The {0} attribute must be defined on member {1}",
                        typeof(T).Name,
                        member.Name));
            }

            return (T)attribute;
        }

        public static string GetPropertyDisplayName<T>(Expression<Func<T, object>> propertyExpression)
        {
            var memberInfo = GetPropertyInformation(propertyExpression.Body);
            if (memberInfo == null)
            {
                throw new ArgumentException(
                    "No property reference expression was found.",
                    "propertyExpression");
            }

            var attr = memberInfo.GetAttribute<DisplayNameAttribute>(false);
            if (attr == null)
            {
                return memberInfo.Name;
            }

            return attr.DisplayName;
        }

        public static MemberInfo GetPropertyInformation(Expression propertyExpression)
        {
            Debug.Assert(propertyExpression != null, "propertyExpression !== null");
            MemberExpression memberExpr = propertyExpression as MemberExpression;
            if (memberExpr == null)
            {
                UnaryExpression unaryExpr = propertyExpression as UnaryExpression;
                if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Convert)
                {
                    memberExpr = unaryExpr.Operand as MemberExpression;
                }
            }

            if (memberExpr != null && memberExpr.Member.MemberType == MemberTypes.Property)
            {
                return memberExpr.Member;
            }

            return null;
        }

        #region EnumExtension
        /// <summary>
        /// Covert 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static T ToEnum<T>(this string value, bool ignoreCase = true)
        {
            return (T)Enum.Parse(typeof(T), value, ignoreCase);
        }

        /// <summary>
        /// get description from enum 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="isRequired">is Attribute Required</param>
        /// <returns></returns>
        public static string GetDescription(this Enum value, bool isRequired = false)
        {
            return value.GetType().
                GetMember(value.ToString()).First()
                .GetAttribute<DescriptionAttribute>(isRequired) is DescriptionAttribute attribute
                ? attribute.Description
                : throw new Exception($"Enum member '{value.GetType()}.{value}' doesn't have a [DescriptionAttribute]!");
        }

        /// <summary>
        /// get enum from description 
        /// https://stackoverflow.com/questions/1799370/getting-attributes-of-enums-value
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="description"></param>
        /// <returns></returns>
        public static T GetEnumValueFromDescription<T>(this string description) where T : Enum
        {
            foreach (FieldInfo fieldInfo in typeof(T).GetFields())
            {
                if (fieldInfo.GetCustomAttribute<DescriptionAttribute>() is DescriptionAttribute attribute && attribute.Description == description)
                    return (T)fieldInfo.GetRawConstantValue();
            }

            throw new Exception($"Enum '{typeof(T)}' doesn't have a member with a [DescriptionAttribute('{description}')]!");
        }
        #endregion

        /// <summary>
        /// https://stackoverflow.com/questions/31261955/nameof-expression-in-net-framework-4
        /// useage: Accessing static properties/fields:
        /// public class myClass
        /// {
        ///     public string myProp { get; set; }
        /// }
        /// var a = new myClass();
        /// var result = a.nameof(b => b.myProp);
        /// TestExtension.nameof(() => myClass.MyOtherField)
        /// Accessing parameters within functions:
        /// void func(int a)
        /// {
        ///     TestExtension.nameof(() => a);
        /// }
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TT"></typeparam>
        /// <param name="accessor"></param>
        /// <returns></returns>
        public static string nameof<T, TT>(this Expression<Func<T, TT>> accessor)
        {
            return nameof(accessor.Body);
        }

        public static string nameof<T>(this Expression<Func<T>> accessor)
        {
            return nameof(accessor.Body);
        }

        public static string NameOf<T, TT>(this T obj, Expression<Func<T, TT>> propertyAccessor)
            where T : BaseEntityModel
        {
            return nameof(propertyAccessor.Body);
        }

        public static string DisplayName<T>(this T model, Expression<Func<T, object>> propertyExpression)
            where T : BaseEntityModel
        {
            return GetPropertyDisplayName(propertyExpression);
        }

        private static string nameof(Expression expression)
        {
            if (expression.NodeType == ExpressionType.MemberAccess)
            {
                var memberExpression = expression as MemberExpression;
                if (memberExpression == null)
                    return null;
                return memberExpression.Member.Name;
            }
            return null;
        }

        public static TResult GetPropertyValue<TResult>(this object t, string propertyName)
        {
            object val = t.GetType().GetProperties().Single(pi => pi.Name == propertyName).GetValue(t, null);
            return (TResult)val;
        }
    }
}
